package gossipServices.network;

import gossipServices.basic.nodeDescriptors.NodeDescriptor;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import networkRmi.NetworkManager;

import messages.Message;
import messages.MessageHandler;

/**
 * 
 * Description:
 * The  GeneralServicesNetManager implements the NetworkServicesAPI and is able 
 * to translate the calls to this interface into the functions of the 
 * NetworkManager.
 * 
 * This class is also able to handle Message(s) 
 * of the Peer Sampling Service, so it implements the MessageHandler
 * interface.
 *
 */
public abstract class GeneralServicesNetManagerOld implements MessageHandler, 
		NetworkServicesAPI {
	
	protected static boolean SHOW_SERVICE_NETMAN_ERRORS = true;
	
	/**
	 * Used to gather messages extracted from the MessageDispatcher
	 * and waiting to be used by the active or passive thread.
	 */
	protected Vector<Message> receivedMessages = new Vector<Message>(100);
	
	/**
	 * Used by the receive(NodeDescriptor) function. It contains all the 
	 * answers NodeDescriptor which the node is waiting for.
	 */
	protected Vector<NodeDescriptor> waitingIncoming = new Vector<NodeDescriptor>(100);
	
	/**
	 * This is a pointer to the local NetworkManager. 
	 * The NetworkServicesAPI performs remote calls through it.
	 */
	protected NetworkManager nm;

	/**
	 * In order to make a non-blocking receive call for the 
	 * active thread, we add a timing value. Elapsed the
	 * time the thread will wake up and continues its operations. 
	 */
	protected int nonBlockingReceiveWaitTime = 1000; //msec 
	
	
	/*
	 * Used during debug. 
	 * One can activate it by setting the level ALL:
	 *   log.setLevel(Level.ALL);
	 */
	protected Logger log = Logger.getLogger("ServiceNetMan_log");
	
	public GeneralServicesNetManagerOld(NetworkManager networkManager) {
		nm = networkManager;
		nm.registerService(this);
		log.setLevel(Level.OFF);
	}

	@Override
	public void send(Message msg, NodeDescriptor node) {
		log.finer(Thread.currentThread().getName()+" sending a message");
		try{
			nm.send(msg, node.getNodeAddress());
		}catch(RemoteException e){
			if(SHOW_SERVICE_NETMAN_ERRORS)
				System.err.println(this.toString()+" "+
						" problem sending "+ msg +" to "+msg.getDest()+
						" thrown a RemoteException");
		} catch (NotBoundException e) {
			if(SHOW_SERVICE_NETMAN_ERRORS)
				System.err.println(this.toString()+
						" problem sending "+ msg +" to "+msg.getDest()+
						" exception is "+ e);
		}
	}

	
	//WARNING: PERIL OF STARVATION!!!!
	//what if the receive(*) call arrives before a 
	//receive(NodeDescriptor) one?
	@Override
	public Message receive(NodeDescriptor node) {
		Message ret = null;
		
		synchronized (receivedMessages) {
			if(!receivedMessages.isEmpty()){
				ret = scanForMessage(node);
			}
		}
//		while(ret == null){
			synchronized (receivedMessages) {
				try {
					waitingIncoming.add(node);
					log.info(Thread.currentThread().getName()+" add waitingIncoming: "+ node +
					" recevedMessages size is: "+receivedMessages.size()+
					" and waitingIncoming is: "+waitingIncoming.size());
					
					receivedMessages.wait(nonBlockingReceiveWaitTime);
					ret = scanForMessage(node);
					receivedMessages.notify();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
//		}
		log.info(Thread.currentThread().getName()+" receive(NodeDescriptor) delivering: "+ret);
		return ret;
	}

	@Override
	public Message receive() {
		Message ret = null;
		if(!receivedMessages.isEmpty()){
			log.finer("found a message: removing from receivedMessages");
			ret = scanForMessageChecked();
		}
		while(ret == null){
			synchronized (receivedMessages) {
				try {
					log.finer(Thread.currentThread().getName()+" waiting for an incoming message...");
					log.finer("recevedMessages size is: "+receivedMessages.size());
					receivedMessages.wait(0);
					if(!receivedMessages.isEmpty()){
						ret = scanForMessageChecked();
					}
					receivedMessages.notify();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		log.info(Thread.currentThread().getName()+" delivering: "+ret);
		return ret;
	}
	
	/**
	 * @return
	 */
	protected Message scanForMessageChecked() {
		Message ret = null;
		for(int i =0; i<receivedMessages.size() && 
		ret == null; i++){
			/*
			 * we need to make sure that the active thread is not 
			 * actually waiting for a specific sender
			 */
			NodeDescriptor sender = receivedMessages.get(i).getSender();
			if(!waitingIncoming.contains(sender)){
				ret = receivedMessages.remove(i);
				log.info(Thread.currentThread().getName()+" remove from waitingIncoming: "+sender);
			}
		}
		return ret;
	}

	/**
	 * @param node 
	 * @return
	 */
	protected Message scanForMessage(NodeDescriptor node) {
		Message ret = null;
		for(int i=0; i<receivedMessages.size() && ret == null; i++){
			NodeDescriptor sender = receivedMessages.get(i).getSender();
			if(sender.equals(node) && waitingIncoming.contains(sender)){
				ret = receivedMessages.remove(i);
				log.info(Thread.currentThread().getName()+" remove from waitingIncoming: "+node);
				waitingIncoming.remove(node);
			}
		}
		return ret;
	}


	@Override
	public String getNodeName() {
		return nm.getNodeName();
	}
	
	protected void enableNetManErrors(){
		SHOW_SERVICE_NETMAN_ERRORS = true;
	}
	
	protected void disableNetManErrors(){
		SHOW_SERVICE_NETMAN_ERRORS = false;
	}
	
	@Override
	public void clearReceivedMessages(){
		receivedMessages.clear();
		waitingIncoming.clear();
	}

}
